﻿#pragma once

#include <dx_core/dx_core.h>
#include <dx_core/frame_resource.h>
#include <dx_utils.h>
#include <vector>

namespace ifire {

///< summary>
/// A Keyframe defines the bone transformation at an instant in time.
///</summary>
struct Keyframe {
  Keyframe();
  ~Keyframe();

  float TimePos;
  DirectX::XMFLOAT3 Translation;
  DirectX::XMFLOAT3 Scale;
  DirectX::XMFLOAT4 RotationQuat;
};

///< summary>
/// A BoneAnimation is defined by a list of keyframes.  For time
/// values inbetween two keyframes, we interpolate between the
/// two nearest keyframes that bound the time.
///
/// We assume an animation always has two keyframes.
///</summary>
struct BoneAnimation {
  float GetStartTime() const;
  float GetEndTime() const;

  void Interpolate(float t, DirectX::XMFLOAT4X4& M) const;

  std::vector<Keyframe> Keyframes;
};

///< summary>
/// Examples of AnimationClips are "Walk", "Run", "Attack", "Defend".
/// An AnimationClip requires a BoneAnimation for every bone to form
/// the animation clip.
///</summary>
struct AnimationClip {
  float GetClipStartTime() const;
  float GetClipEndTime() const;

  void Interpolate(
      float t, std::vector<DirectX::XMFLOAT4X4>& boneTransforms) const;

  std::vector<BoneAnimation> BoneAnimations;
};

class SkinnedData {
public:
  UINT BoneCount() const;

  float GetClipStartTime(const std::string& clipName) const;
  float GetClipEndTime(const std::string& clipName) const;

  void Set(std::vector<int>& boneHierarchy,
      std::vector<DirectX::XMFLOAT4X4>& boneOffsets,
      std::unordered_map<std::string, AnimationClip>& animations);

  // In a real project, you'd want to cache the result if there was a chance
  // that you were calling this several times with the same clipName at
  // the same timePos.
  void GetFinalTransforms(const std::string& clipName, float timePos,
      std::vector<DirectX::XMFLOAT4X4>& finalTransforms) const;

private:
  // Gives parentIndex of ith bone.
  std::vector<int> mBoneHierarchy;

  std::vector<DirectX::XMFLOAT4X4> mBoneOffsets;

  std::unordered_map<std::string, AnimationClip> mAnimations;
};


struct SkinnedModelInstance
{
    SkinnedData* SkinnedInfo = nullptr;
    std::vector<DirectX::XMFLOAT4X4> FinalTransforms;
    std::string ClipName;
    float TimePos = 0.0f;

    // Called every frame and increments the time position, interpolates the 
    // animations for each bone based on the current animation clip, and 
    // generates the final transforms which are ultimately set to the effect
    // for processing in the vertex shader.
    void UpdateSkinnedAnimation(float dt)
    {
        TimePos += dt;

        // Loop animation
        if(TimePos > SkinnedInfo->GetClipEndTime(ClipName))
            TimePos = 0.0f;

        // Compute the final transforms for this time position.
        SkinnedInfo->GetFinalTransforms(ClipName, TimePos, FinalTransforms);
    }
};

} // namespace ifire